/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
 
This file is part of Quake III Arena source code.
 
Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
 
Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
//
// cg_servercmds.c -- reliably sequenced text commands sent by the server
// these are processed at snapshot transition time, so there will definately
// be a valid snapshot this frame

#include "cg_local.h"
#include "../ui/menudef.h" // bk001205 - for Q3_ui as well

typedef struct
{
    const char *order;
    int taskNum;
}
orderTask_t;

static const orderTask_t validOrders[] =
    {
        {
            VOICECHAT_GETFLAG,						TEAMTASK_OFFENSE
        },
        { VOICECHAT_OFFENSE,						TEAMTASK_OFFENSE },
        { VOICECHAT_DEFEND,							TEAMTASK_DEFENSE },
        { VOICECHAT_DEFENDFLAG,					TEAMTASK_DEFENSE },
        { VOICECHAT_PATROL,							TEAMTASK_PATROL },
        { VOICECHAT_CAMP,								TEAMTASK_CAMP },
        { VOICECHAT_FOLLOWME,						TEAMTASK_FOLLOW },
        { VOICECHAT_RETURNFLAG,					TEAMTASK_RETRIEVE },
        { VOICECHAT_FOLLOWFLAGCARRIER,	TEAMTASK_ESCORT }
    };

static const int numValidOrders = sizeof(validOrders) / sizeof(orderTask_t);

/*
=================
CG_ParseScores
 
=================
*/
static void CG_ParseScores( void )
{
    int		i, powerups;

    cg.numScores = atoi( CG_Argv( 1 ) );
    if ( cg.numScores > MAX_CLIENTS )
    {
        cg.numScores = MAX_CLIENTS;
    }

    cg.teamScores[0] = atoi( CG_Argv( 2 ) );
    cg.teamScores[1] = atoi( CG_Argv( 3 ) );

    memset( cg.scores, 0, sizeof( cg.scores ) );
    for ( i = 0 ; i < cg.numScores ; i++ )
    {
        //
        cg.scores[i].client = atoi( CG_Argv( i * 14 + 4 ) );
        cg.scores[i].score = atoi( CG_Argv( i * 14 + 5 ) );
        cg.scores[i].ping = atoi( CG_Argv( i * 14 + 6 ) );
        cg.scores[i].time = atoi( CG_Argv( i * 14 + 7 ) );
        cg.scores[i].scoreFlags = atoi( CG_Argv( i * 14 + 8 ) );
        powerups = atoi( CG_Argv( i * 14 + 9 ) );
        cg.scores[i].accuracy = atoi(CG_Argv(i * 14 + 10));
        cg.scores[i].impressiveCount = atoi(CG_Argv(i * 14 + 11));
        cg.scores[i].excellentCount = atoi(CG_Argv(i * 14 + 12));
        cg.scores[i].guantletCount = atoi(CG_Argv(i * 14 + 13));
        cg.scores[i].defendCount = atoi(CG_Argv(i * 14 + 14));
        cg.scores[i].assistCount = atoi(CG_Argv(i * 14 + 15));
        cg.scores[i].perfect = atoi(CG_Argv(i * 14 + 16));
        cg.scores[i].captures = atoi(CG_Argv(i * 14 + 17));

        if ( cg.scores[i].client < 0 || cg.scores[i].client >= MAX_CLIENTS )
        {
            cg.scores[i].client = 0;
        }
        cgs.clientinfo[ cg.scores[i].client ].score = cg.scores[i].score;
        cgs.clientinfo[ cg.scores[i].client ].powerups = powerups;

        cg.scores[i].team = cgs.clientinfo[cg.scores[i].client].team;
    }
}

/*
=================
CG_ParseTeamInfo
 
=================
*/
static void CG_ParseTeamInfo( void )
{
    int		i;
    int		client;

    numSortedTeamPlayers = atoi( CG_Argv( 1 ) );

    for ( i = 0 ; i < numSortedTeamPlayers ; i++ )
    {
        client = atoi( CG_Argv( i * 6 + 2 ) );

        sortedTeamPlayers[i] = client;

        cgs.clientinfo[ client ].location = atoi( CG_Argv( i * 6 + 3 ) );
        cgs.clientinfo[ client ].health = atoi( CG_Argv( i * 6 + 4 ) );
        cgs.clientinfo[ client ].armor = atoi( CG_Argv( i * 6 + 5 ) );
        cgs.clientinfo[ client ].curWeapon = atoi( CG_Argv( i * 6 + 6 ) );
        cgs.clientinfo[ client ].powerups = atoi( CG_Argv( i * 6 + 7 ) );
    }
}


/*
================
CG_ParseServerinfo
 
This is called explicitly when the gamestate is first received,
and whenever the server updates any serverinfo flagged cvars
================
*/
void CG_ParseServerinfo( void )
{
    const char	*info;
    char	*mapname;

    info = CG_ConfigString( CS_SERVERINFO );
    cgs.gametype = (gametype_t)atoi( Info_ValueForKey( info, "g_gametype" ) );
    Cvar_Set("g_gametype", va("%i", cgs.gametype));
    cgs.dmflags = atoi( Info_ValueForKey( info, "dmflags" ) );
    cgs.teamflags = atoi( Info_ValueForKey( info, "teamflags" ) );
    cgs.fraglimit = atoi( Info_ValueForKey( info, "fraglimit" ) );
    cgs.capturelimit = atoi( Info_ValueForKey( info, "capturelimit" ) );
    cgs.timelimit = atoi( Info_ValueForKey( info, "timelimit" ) );
    cgs.maxclients = atoi( Info_ValueForKey( info, "sv_maxclients" ) );
    mapname = Info_ValueForKey( info, "mapname" );
    Com_sprintf( cgs.mapname, sizeof( cgs.mapname ), "maps/%s.bsp", mapname );
    Q_strncpyz( cgs.redTeam, Info_ValueForKey( info, "g_redTeam" ), sizeof(cgs.redTeam) );
    Cvar_Set("g_redTeam", cgs.redTeam);
    Q_strncpyz( cgs.blueTeam, Info_ValueForKey( info, "g_blueTeam" ), sizeof(cgs.blueTeam) );
    Cvar_Set("g_blueTeam", cgs.blueTeam);
}

/*
==================
CG_ParseWarmup
==================
*/
static void CG_ParseWarmup( void )
{
    const char	*info;
    int			warmup;

    info = CG_ConfigString( CS_WARMUP );

    warmup = atoi( info );
    cg.warmupCount = -1;

    if ( warmup == 0 && cg.warmup )
    {}
    else if ( warmup > 0 && cg.warmup <= 0 )
    {
            S_StartLocalSound( cgs.media.countPrepareSound, CHAN_ANNOUNCER );
    }

    cg.warmup = warmup;
}

/*
================
CG_SetConfigValues
 
Called on load to set the initial values from configure strings
================
*/
void CG_SetConfigValues( void )
{
    const char *s;

    cgs.scores1 = atoi( CG_ConfigString( CS_SCORES1 ) );
    cgs.scores2 = atoi( CG_ConfigString( CS_SCORES2 ) );
    cgs.levelStartTime = atoi( CG_ConfigString( CS_LEVEL_START_TIME ) );
    if( cgs.gametype == GT_CTF )
    {
        s = CG_ConfigString( CS_FLAGSTATUS );
        cgs.redflag = s[0] - '0';
        cgs.blueflag = s[1] - '0';
    }
    cg.warmup = atoi( CG_ConfigString( CS_WARMUP ) );
}

/*
=====================
CG_ShaderStateChanged
=====================
*/
void CG_ShaderStateChanged(void)
{
    char originalShader[MAX_QPATH];
    char newShader[MAX_QPATH];
    char timeOffset[16];
    const char *o;
    char *n,*t;

    o = CG_ConfigString( CS_SHADERSTATE );
    while (o && *o)
    {
        n = strstr(o, "=");
        if (n && *n)
        {
            strncpy(originalShader, o, n-o);
            originalShader[n-o] = 0;
            n++;
            t = strstr(n, ":");
            if (t && *t)
            {
                strncpy(newShader, n, t-n);
                newShader[t-n] = 0;
            }
            else
            {
                break;
            }
            t++;
            o = strstr(t, "@");
            if (o)
            {
                strncpy(timeOffset, t, o-t);
                timeOffset[o-t] = 0;
                o++;
                R_RemapShader( originalShader, newShader, timeOffset );
            }
        }
        else
        {
            break;
        }
    }
}

/*
================
CG_ConfigStringModified
 
================
*/
static void CG_ConfigStringModified( void )
{
    const char	*str;
    int		num;

    num = atoi( CG_Argv( 1 ) );

    // get the gamestate from the client system, which will have the
    // new configstring already integrated
    CL_GetGameState( &cgs.gameState );

    // look up the individual string that was modified
    str = CG_ConfigString( num );

    // do something with it if necessary
    if ( num == CS_MUSIC )
    {
        CG_StartMusic();
    }
    else if ( num == CS_SERVERINFO )
    {
        CG_ParseServerinfo();
    }
    else if ( num == CS_WARMUP )
    {
        CG_ParseWarmup();
    }
    else if ( num == CS_SCORES1 )
    {
        cgs.scores1 = atoi( str );
    }
    else if ( num == CS_SCORES2 )
    {
        cgs.scores2 = atoi( str );
    }
    else if ( num == CS_LEVEL_START_TIME )
    {
        cgs.levelStartTime = atoi( str );
    }
    else if ( num == CS_VOTE_TIME )
    {
        cgs.voteTime = atoi( str );
        cgs.voteModified = qtrue;
    }
    else if ( num == CS_VOTE_YES )
    {
        cgs.voteYes = atoi( str );
        cgs.voteModified = qtrue;
    }
    else if ( num == CS_VOTE_NO )
    {
        cgs.voteNo = atoi( str );
        cgs.voteModified = qtrue;
    }
    else if ( num == CS_VOTE_STRING )
    {
        Q_strncpyz( cgs.voteString, str, sizeof( cgs.voteString ) );
    }
    else if ( num >= CS_TEAMVOTE_TIME && num <= CS_TEAMVOTE_TIME + 1)
    {
        cgs.teamVoteTime[num-CS_TEAMVOTE_TIME] = atoi( str );
        cgs.teamVoteModified[num-CS_TEAMVOTE_TIME] = qtrue;
    }
    else if ( num >= CS_TEAMVOTE_YES && num <= CS_TEAMVOTE_YES + 1)
    {
        cgs.teamVoteYes[num-CS_TEAMVOTE_YES] = atoi( str );
        cgs.teamVoteModified[num-CS_TEAMVOTE_YES] = qtrue;
    }
    else if ( num >= CS_TEAMVOTE_NO && num <= CS_TEAMVOTE_NO + 1)
    {
        cgs.teamVoteNo[num-CS_TEAMVOTE_NO] = atoi( str );
        cgs.teamVoteModified[num-CS_TEAMVOTE_NO] = qtrue;
    }
    else if ( num >= CS_TEAMVOTE_STRING && num <= CS_TEAMVOTE_STRING + 1)
    {
        Q_strncpyz( cgs.teamVoteString[num-CS_TEAMVOTE_STRING], str, sizeof( cgs.teamVoteString ) );
    }
    else if ( num == CS_INTERMISSION )
    {
        cg.intermissionStarted = atoi( str );
    }
    else if ( num >= CS_MODELS && num < CS_MODELS+MAX_MODELS )
    {
        cgs.gameModels[ num-CS_MODELS ] = RE_RegisterModel( str );
    }
    else if ( num >= CS_SOUNDS && num < CS_SOUNDS+MAX_MODELS )
    {
        if ( str[0] != '*' )
        {	// player specific sounds don't register here
            cgs.gameSounds[ num-CS_SOUNDS] = S_RegisterSound( str, qfalse );
        }
    }
    else if ( num >= CS_PLAYERS && num < CS_PLAYERS+MAX_CLIENTS )
    {
        CG_NewClientInfo( num - CS_PLAYERS );
        CG_BuildSpectatorString();
    }
    else if ( num == CS_FLAGSTATUS )
    {
        if( cgs.gametype == GT_CTF )
        {
            // format is rb where its red/blue, 0 is at base, 1 is taken, 2 is dropped
            cgs.redflag = str[0] - '0';
            cgs.blueflag = str[1] - '0';
        }
    }
    else if ( num == CS_SHADERSTATE )
    {
        CG_ShaderStateChanged();
    }

}


/*
=======================
CG_AddToTeamChat
 
=======================
*/
static void CG_AddToTeamChat( const char *str )
{
    int len;
    char *p, *ls;
    int lastcolor;
    int chatHeight;

    if (cg_teamChatHeight->integer < TEAMCHAT_HEIGHT)
    {
        chatHeight = cg_teamChatHeight->integer;
    }
    else
    {
        chatHeight = TEAMCHAT_HEIGHT;
    }

    if (chatHeight <= 0 || cg_teamChatTime->integer <= 0)
    {
        // team chat disabled, dump into normal chat
        cgs.teamChatPos = cgs.teamLastChatPos = 0;
        return;
    }

    len = 0;

    p = cgs.teamChatMsgs[cgs.teamChatPos % chatHeight];
    *p = 0;

    lastcolor = '7';

    ls = NULL;
    while (*str)
    {
        if (len > TEAMCHAT_WIDTH - 1)
        {
            if (ls)
            {
                str -= (p - ls);
                str++;
                p -= (p - ls);
            }
            *p = 0;

            cgs.teamChatMsgTimes[cgs.teamChatPos % chatHeight] = cg.time;

            cgs.teamChatPos++;
            p = cgs.teamChatMsgs[cgs.teamChatPos % chatHeight];
            *p = 0;
            *p++ = Q_COLOR_ESCAPE;
            *p++ = lastcolor;
            len = 0;
            ls = NULL;
        }

        if ( Q_IsColorString( str ) )
        {
            *p++ = *str++;
            lastcolor = *str;
            *p++ = *str++;
            continue;
        }
        if (*str == ' ')
        {
            ls = p;
        }
        *p++ = *str++;
        len++;
    }
    *p = 0;

    cgs.teamChatMsgTimes[cgs.teamChatPos % chatHeight] = cg.time;
    cgs.teamChatPos++;

    if (cgs.teamChatPos - cgs.teamLastChatPos > chatHeight)
        cgs.teamLastChatPos = cgs.teamChatPos - chatHeight;
}

/*
===============
CG_MapRestart
 
The server has issued a map_restart, so the next snapshot
is completely new and should not be interpolated to.
 
A tournement restart will clear everything, but doesn't
require a reload of all the media
===============
*/
static void CG_MapRestart( void )
{
    if ( cg_showmiss->integer )
    {
        Com_Printf( "CG_MapRestart\n" );
    }

    CG_InitLocalEntities();
    CG_InitMarkPolys();
    CG_ClearParticles ();

    // make sure the "3 frags left" warnings play again
    cg.fraglimitWarnings = 0;

    cg.timelimitWarnings = 0;

    cg.intermissionStarted = qfalse;

    cgs.voteTime = 0;

    cg.mapRestart = qtrue;

    CG_StartMusic();

    S_ClearLoopingSounds(qtrue);

    // we really should clear more parts of cg here and stop sounds

    // play the "fight" sound if this is a restart without warmup
    if ( cg.warmup == 0 /* && cgs.gametype == GT_TOURNAMENT */)
    {
        S_StartLocalSound( cgs.media.countFightSound, CHAN_ANNOUNCER );
        CG_CenterPrint( "FIGHT!", 120, GIANTCHAR_WIDTH*2 );
    }
    Cvar_Set("cg_thirdPerson", "0");
}

#define MAX_VOICEFILESIZE	16384
#define MAX_VOICEFILES		8
#define MAX_VOICECHATS		64
#define MAX_VOICESOUNDS		64
#define MAX_CHATSIZE		64
#define MAX_HEADMODELS		64

typedef struct voiceChat_s
{
    char id[64];
    int numSounds;
    sfxHandle_t sounds[MAX_VOICESOUNDS];
    char chats[MAX_VOICESOUNDS][MAX_CHATSIZE];
}
voiceChat_t;

typedef struct voiceChatList_s
{
    char name[64];
    int gender;
    int numVoiceChats;
    voiceChat_t voiceChats[MAX_VOICECHATS];
}
voiceChatList_t;

typedef struct headModelVoiceChat_s
{
    char headmodel[64];
    int voiceChatNum;
}
headModelVoiceChat_t;

voiceChatList_t voiceChatLists[MAX_VOICEFILES];
headModelVoiceChat_t headModelVoiceChat[MAX_HEADMODELS];

/*
=================
CG_ParseVoiceChats
=================
*/
int CG_ParseVoiceChats( const char *filename, voiceChatList_t *voiceChatList, int maxVoiceChats )
{
    int	len, i;
    fileHandle_t f;
    char buf[MAX_VOICEFILESIZE];
    char **p, *ptr;
    char *token;
    voiceChat_t *voiceChats;
    qboolean compress;
    sfxHandle_t sound;

    compress = qtrue;
    if (cg_buildScript->integer)
    {
        compress = qfalse;
    }

    len = FS_FOpenFileByMode( filename, &f, FS_READ );
    if ( !f )
    {
        Com_Printf( va( S_COLOR_RED "voice chat file not found: %s\n", filename ) );
        return qfalse;
    }
    if ( len >= MAX_VOICEFILESIZE )
    {
        Com_Printf( va( S_COLOR_RED "voice chat file too large: %s is %i, max allowed is %i", filename, len, MAX_VOICEFILESIZE ) );
        FS_FCloseFile( f );
        return qfalse;
    }

    FS_Read2( buf, len, f );
    buf[len] = 0;
    FS_FCloseFile( f );

    ptr = buf;
    p = &ptr;

    Com_sprintf(voiceChatList->name, sizeof(voiceChatList->name), "%s", filename);
    voiceChats = voiceChatList->voiceChats;
    for ( i = 0; i < maxVoiceChats; i++ )
    {
        voiceChats[i].id[0] = 0;
    }
    token = COM_ParseExt(p, qtrue);
    if (!token || token[0] == 0)
    {
        return qtrue;
    }
    if (!Q_stricmp(token, "female"))
    {
        voiceChatList->gender = GENDER_FEMALE;
    }
    else if (!Q_stricmp(token, "male"))
    {
        voiceChatList->gender = GENDER_MALE;
    }
    else if (!Q_stricmp(token, "neuter"))
    {
        voiceChatList->gender = GENDER_NEUTER;
    }
    else
    {
        Com_Printf( va( S_COLOR_RED "expected gender not found in voice chat file: %s\n", filename ) );
        return qfalse;
    }

    voiceChatList->numVoiceChats = 0;
    while ( 1 )
    {
        token = COM_ParseExt(p, qtrue);
        if (!token || token[0] == 0)
        {
            return qtrue;
        }
        Com_sprintf(voiceChats[voiceChatList->numVoiceChats].id, sizeof( voiceChats[voiceChatList->numVoiceChats].id ), "%s", token);
        token = COM_ParseExt(p, qtrue);
        if (Q_stricmp(token, "{"))
        {
            Com_Printf( va( S_COLOR_RED "expected { found %s in voice chat file: %s\n", token, filename ) );
            return qfalse;
        }
        voiceChats[voiceChatList->numVoiceChats].numSounds = 0;
        while(1)
        {
            token = COM_ParseExt(p, qtrue);
            if (!token || token[0] == 0)
            {
                return qtrue;
            }
            if (!Q_stricmp(token, "}"))
                break;
            sound = S_RegisterSound( token, compress );
            voiceChats[voiceChatList->numVoiceChats].sounds[voiceChats[voiceChatList->numVoiceChats].numSounds] = sound;
            token = COM_ParseExt(p, qtrue);
            if (!token || token[0] == 0)
            {
                return qtrue;
            }
            Com_sprintf(voiceChats[voiceChatList->numVoiceChats].chats[
                            voiceChats[voiceChatList->numVoiceChats].numSounds], MAX_CHATSIZE, "%s", token);
            if (sound)
                voiceChats[voiceChatList->numVoiceChats].numSounds++;
            if (voiceChats[voiceChatList->numVoiceChats].numSounds >= MAX_VOICESOUNDS)
                break;
        }
        voiceChatList->numVoiceChats++;
        if (voiceChatList->numVoiceChats >= maxVoiceChats)
            return qtrue;
    }
    return qtrue;
}

/*
=================
CG_LoadVoiceChats
=================
*/
void CG_LoadVoiceChats( void )
{
    int size;

    size = Hunk_MemoryRemaining();
    CG_ParseVoiceChats( "scripts/female1.voice", &voiceChatLists[0], MAX_VOICECHATS );
    CG_ParseVoiceChats( "scripts/female2.voice", &voiceChatLists[1], MAX_VOICECHATS );
    CG_ParseVoiceChats( "scripts/female3.voice", &voiceChatLists[2], MAX_VOICECHATS );
    CG_ParseVoiceChats( "scripts/male1.voice", &voiceChatLists[3], MAX_VOICECHATS );
    CG_ParseVoiceChats( "scripts/male2.voice", &voiceChatLists[4], MAX_VOICECHATS );
    CG_ParseVoiceChats( "scripts/male3.voice", &voiceChatLists[5], MAX_VOICECHATS );
    CG_ParseVoiceChats( "scripts/male4.voice", &voiceChatLists[6], MAX_VOICECHATS );
    CG_ParseVoiceChats( "scripts/male5.voice", &voiceChatLists[7], MAX_VOICECHATS );
    Com_Printf("voice chat memory size = %d\n", size - Hunk_MemoryRemaining());
}

/*
=================
CG_HeadModelVoiceChats
=================
*/
int CG_HeadModelVoiceChats( char *filename )
{
    int	len, i;
    fileHandle_t f;
    char buf[MAX_VOICEFILESIZE];
    char **p, *ptr;
    char *token;

    len = FS_FOpenFileByMode( filename, &f, FS_READ );
    if ( !f )
    {
        //Com_Printf( va( "voice chat file not found: %s\n", filename ) );
        return -1;
    }
    if ( len >= MAX_VOICEFILESIZE )
    {
        Com_Printf( va( S_COLOR_RED "voice chat file too large: %s is %i, max allowed is %i", filename, len, MAX_VOICEFILESIZE ) );
        FS_FCloseFile( f );
        return -1;
    }

    FS_Read2( buf, len, f );
    buf[len] = 0;
    FS_FCloseFile( f );

    ptr = buf;
    p = &ptr;

    token = COM_ParseExt(p, qtrue);
    if (!token || token[0] == 0)
    {
        return -1;
    }

    for ( i = 0; i < MAX_VOICEFILES; i++ )
    {
        if ( !Q_stricmp(token, voiceChatLists[i].name) )
        {
            return i;
        }
    }

    //FIXME: maybe try to load the .voice file which name is stored in token?

    return -1;
}


/*
=================
CG_GetVoiceChat
=================
*/
int CG_GetVoiceChat( voiceChatList_t *voiceChatList, const char *id, sfxHandle_t *snd, char **chat)
{
    int i, rnd;

    for ( i = 0; i < voiceChatList->numVoiceChats; i++ )
    {
        if ( !Q_stricmp( id, voiceChatList->voiceChats[i].id ) )
        {
            rnd = random() * voiceChatList->voiceChats[i].numSounds;
            *snd = voiceChatList->voiceChats[i].sounds[rnd];
            *chat = voiceChatList->voiceChats[i].chats[rnd];
            return qtrue;
        }
    }
    return qfalse;
}

/*
=================
CG_VoiceChatListForClient
=================
*/
voiceChatList_t *CG_VoiceChatListForClient( int clientNum )
{
    clientInfo_t *ci;
    int voiceChatNum, i, j, k, gender;
    char filename[MAX_QPATH], headModelName[MAX_QPATH];

    if ( clientNum < 0 || clientNum >= MAX_CLIENTS )
    {
        clientNum = 0;
    }
    ci = &cgs.clientinfo[ clientNum ];

    for ( k = 0; k < 2; k++ )
    {
        if ( k == 0 )
        {
            if (ci->headModelName[0] == '*')
            {
                Com_sprintf( headModelName, sizeof(headModelName), "%s/%s", ci->headModelName+1, ci->headSkinName );
            }
            else
            {
                Com_sprintf( headModelName, sizeof(headModelName), "%s/%s", ci->headModelName, ci->headSkinName );
            }
        }
        else
        {
            if (ci->headModelName[0] == '*')
            {
                Com_sprintf( headModelName, sizeof(headModelName), "%s", ci->headModelName+1 );
            }
            else
            {
                Com_sprintf( headModelName, sizeof(headModelName), "%s", ci->headModelName );
            }
        }
        // find the voice file for the head model the client uses
        for ( i = 0; i < MAX_HEADMODELS; i++ )
        {
            if (!Q_stricmp(headModelVoiceChat[i].headmodel, headModelName))
            {
                break;
            }
        }
        if (i < MAX_HEADMODELS)
        {
            return &voiceChatLists[headModelVoiceChat[i].voiceChatNum];
        }
        // find a <headmodelname>.vc file
        for ( i = 0; i < MAX_HEADMODELS; i++ )
        {
            if (!strlen(headModelVoiceChat[i].headmodel))
            {
                Com_sprintf(filename, sizeof(filename), "scripts/%s.vc", headModelName);
                voiceChatNum = CG_HeadModelVoiceChats(filename);
                if (voiceChatNum == -1)
                    break;
                Com_sprintf(headModelVoiceChat[i].headmodel, sizeof ( headModelVoiceChat[i].headmodel ),
                            "%s", headModelName);
                headModelVoiceChat[i].voiceChatNum = voiceChatNum;
                return &voiceChatLists[headModelVoiceChat[i].voiceChatNum];
            }
        }
    }
    gender = ci->gender;
    for (k = 0; k < 2; k++)
    {
        // just pick the first with the right gender
        for ( i = 0; i < MAX_VOICEFILES; i++ )
        {
            if (strlen(voiceChatLists[i].name))
            {
                if (voiceChatLists[i].gender == gender)
                {
                    // store this head model with voice chat for future reference
                    for ( j = 0; j < MAX_HEADMODELS; j++ )
                    {
                        if (!strlen(headModelVoiceChat[j].headmodel))
                        {
                            Com_sprintf(headModelVoiceChat[j].headmodel, sizeof ( headModelVoiceChat[j].headmodel ),
                                        "%s", headModelName);
                            headModelVoiceChat[j].voiceChatNum = i;
                            break;
                        }
                    }
                    return &voiceChatLists[i];
                }
            }
        }
        // fall back to male gender because we don't have neuter in the mission pack
        if (gender == GENDER_MALE)
            break;
        gender = GENDER_MALE;
    }
    // store this head model with voice chat for future reference
    for ( j = 0; j < MAX_HEADMODELS; j++ )
    {
        if (!strlen(headModelVoiceChat[j].headmodel))
        {
            Com_sprintf(headModelVoiceChat[j].headmodel, sizeof ( headModelVoiceChat[j].headmodel ),
                        "%s", headModelName);
            headModelVoiceChat[j].voiceChatNum = 0;
            break;
        }
    }
    // just return the first voice chat list
    return &voiceChatLists[0];
}

#define MAX_VOICECHATBUFFER		32

typedef struct bufferedVoiceChat_s
{
    int clientNum;
    sfxHandle_t snd;
    int voiceOnly;
    char cmd[MAX_SAY_TEXT];
    char message[MAX_SAY_TEXT];
}
bufferedVoiceChat_t;

bufferedVoiceChat_t voiceChatBuffer[MAX_VOICECHATBUFFER];

/*
=================
CG_PlayVoiceChat
=================
*/
void CG_PlayVoiceChat( bufferedVoiceChat_t *vchat )
{
}

/*
=====================
CG_PlayBufferedVoieChats
=====================
*/
void CG_PlayBufferedVoiceChats( void )
{
}

/*
=====================
CG_AddBufferedVoiceChat
=====================
*/
void CG_AddBufferedVoiceChat( bufferedVoiceChat_t *vchat )
{
}

/*
=================
CG_VoiceChatLocal
=================
*/
void CG_VoiceChatLocal( int mode, qboolean voiceOnly, int clientNum, int color, const char *cmd )
{
}

/*
=================
CG_VoiceChat
=================
*/
void CG_VoiceChat( int mode )
{
}

/*
=================
CG_RemoveChatEscapeChar
=================
*/
static void CG_RemoveChatEscapeChar( char *text )
{
    int i, l;

    l = 0;
    for ( i = 0; text[i]; i++ )
    {
        if (text[i] == '\x19')
            continue;
        text[l++] = text[i];
    }
    text[l] = '\0';
}

/*
=================
CG_ServerCommand
 
The string has been tokenized and can be retrieved with
Cmd_Argc() / Cmd_Argv()
=================
*/
static void CG_ServerCommand( void )
{
    const char	*cmd;
    char		text[MAX_SAY_TEXT];

    cmd = CG_Argv(0);

    if ( !cmd[0] )
    {
        // server claimed the command
        return;
    }

    if ( !strcmp( cmd, "cp" ) )
    {
        CG_CenterPrint( CG_Argv(1), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH );
        return;
    }

    if ( !strcmp( cmd, "cs" ) )
    {
        CG_ConfigStringModified();
        return;
    }

    if ( !strcmp( cmd, "print" ) )
    {
        Com_Printf( "%s", CG_Argv(1) );
        return;
    }

    if ( !strcmp( cmd, "chat" ) )
    {
        if ( !cg_teamChatsOnly->integer )
        {
            S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
            Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT );
            CG_RemoveChatEscapeChar( text );
            Com_Printf( "%s\n", text );
        }
        return;
    }

    if ( !strcmp( cmd, "tchat" ) )
    {
        S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
        Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT );
        CG_RemoveChatEscapeChar( text );
        CG_AddToTeamChat( text );
        Com_Printf( "%s\n", text );
        return;
    }
    if ( !strcmp( cmd, "vchat" ) )
    {
        CG_VoiceChat( SAY_ALL );
        return;
    }

    if ( !strcmp( cmd, "vtchat" ) )
    {
        CG_VoiceChat( SAY_TEAM );
        return;
    }

    if ( !strcmp( cmd, "vtell" ) )
    {
        CG_VoiceChat( SAY_TELL );
        return;
    }

    if ( !strcmp( cmd, "scores" ) )
    {
        CG_ParseScores();
        return;
    }

    if ( !strcmp( cmd, "tinfo" ) )
    {
        CG_ParseTeamInfo();
        return;
    }

    if ( !strcmp( cmd, "map_restart" ) )
    {
        CG_MapRestart();
        return;
    }

    if ( Q_stricmp (cmd, "remapShader") == 0 )
    {
        if (Cmd_Argc() == 4)
        {
            R_RemapShader(CG_Argv(1), CG_Argv(2), CG_Argv(3));
        }
    }

    // loaddeferred can be both a servercmd and a consolecmd
    if ( !strcmp( cmd, "loaddefered" ) )
    {	// FIXME: spelled wrong, but not changing for demo
        CG_LoadDeferredPlayers();
        return;
    }

    // clientLevelShot is sent before taking a special screenshot for
    // the menu system during development
    if ( !strcmp( cmd, "clientLevelShot" ) )
    {
        cg.levelShot = qtrue;
        return;
    }

    Com_Printf( "Unknown client game command: %s\n", cmd );
}


/*
====================
CG_ExecuteNewServerCommands
 
Execute all of the server commands that were received along
with this this snapshot.
====================
*/
void CG_ExecuteNewServerCommands( int latestSequence )
{
    while ( cgs.serverCommandSequence < latestSequence )
    {
        if ( CL_GetServerCommand( ++cgs.serverCommandSequence ) )
        {
            CG_ServerCommand();
        }
    }
}
